package asa;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.TreeMap;
import java.util.Vector;

import corpus.ParseException;

public class Options extends Properties {

	private static final long serialVersionUID = -7043334163854483934L;
	private String dateend;
	private String datestart;

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public synchronized Enumeration keys() {
		Enumeration keysEnum = super.keys();
		Vector keyList = new Vector();
		while (keysEnum.hasMoreElements()) {
			keyList.add(keysEnum.nextElement());
		}
		Collections.sort(keyList);
		return keyList.elements();
	}

	private TreeMap<String, String> info;
	private TreeMap<String, String[]> valid;

	public Options() {
		info = new TreeMap<String, String>();
		valid = new TreeMap<String, String[]>();

		addConfItem("run", "classify", "Which module to run.", new String[] {
				"train", "classify", "makeconf" });
		addConfItem("features", "lemma, pos, slength, dict",
				"Specify which features to use in classifying.", new String[] {
						"list", "lemma", "pos", "slength", "dict", "reversal", "srl" });
		addConfItem("features.generate", "true",
				"Generate new feature lists or use cached. "
						+ "You must use the same feature list for training"
						+ " and test set.", new String[] { "true", "false" });
		addConfItem("models", "models", "Path to models",
				new String[] { "dir" });
		addConfItem("conf", "conf.ini", "Path to config file.",
				new String[] { "file" });
		addConfItem("loadold", "true", "Load old cached data.", new String[] {
				"true", "false" });
		addConfItem("parser", "stanford", "Specify which parser to use. "
				+ "\n\tUse the same parser for training and classifying.",
				new String[] { "stanford", "opennlp","lthsrl", "null" });
		addConfItem("input", "stdin", "What scheme to read input from.",
				new String[] { "list", "stdin", "corpus", "qlikview", "mockup",
						"bug", "twitter" });
		addConfItem("input.dbname",
				"jdbc:jtds:sqlserver://rd-sentiment:1433/communityServerLive",
				"Path to input database", new String[] { "string" });
		addConfItem("input.dbuser", "MrNugues", "Input database user name.",
				new String[] { "string" });
		addConfItem("input.dbpass", "Mr.Nugues2011",
				"Input database password.", new String[] { "string" });
		addConfItem("input.datestart", "2011-01-01",
				"Start date from which start reading.", new String[] { "date" });
		addConfItem("input.dateend", "today",
				"End date from which end reading. "
						+ "\nCan be set to today or YYYY-MM-DD",
				new String[] { "date" });
		addConfItem("output", "stdout", "What format to output data to.",
				new String[] { "stdout", "database", "evaluate", "qvx",
						"spreadsheet" });
		addConfItem("output.dbname", "jdbc:mysql://rd-sentiment:3306/asa",
				"Path to database.", new String[] { "string" });
		addConfItem("output.dbuser", "root", "Database user name.",
				new String[] { "string" });
		addConfItem("output.dbpass", "password", "Database password.",
				new String[] { "string" });
		addConfItem("output.delim", ";",
				"Output file delimiter, for use with spreadsheet output.",
				new String[] { "string" });
		addConfItem("output.file", "QVfiles/Asa.csv",
				"Output file used by some outputs, ie spreadsheet.",
				new String[] { "string" });
		addConfItem("output.dir", "QVfiles",
				"Added to output.file and used by some outputs, ie qvx.",
				new String[] { "dir" });
		addConfItem("input.path", "corpus/polsent/test.txt",
				"Input path used by some crawler, ie corpus."
						+ "\nIt can be a directory or file.",
				new String[] { "string" });
		addConfItem("ner", "regexp", "Named Entity Recognition.", new String[] {
				"regexp", "null", "stanford" });
		addConfItem("ner.regexp", "models/regexp/ner.txt",
				"File with regexp rules used by NER.", new String[] { "file" });
		addConfItem("ml", "arff", "Format for output data and classifying.",
				new String[] { "arff", "libsvm", "hcrf", "vote", "flip", "null" });
		addConfItem("pipeline.threads", "2",
				"Number of threads for classifying and parsing forum posts.",
				new String[] { "nbr" });
		addConfItem("translation", "null", "Translation.", new String[] {
				"remove", "null" });
		addConfItem("translation.threads", "1",
				"Number of threads for translation.", new String[] { "nbr" });
	}

	/**
	 * Parse a config file.
	 * 
	 * @throws java.text.ParseException
	 */
	public void readFile(File conf) throws IOException,
			java.text.ParseException {
		FileInputStream fis = new FileInputStream(conf);
		load(fis);
		for (Entry<Object, Object> e : entrySet()) {
			String value = (String) e.getValue();
			String key = (String) e.getKey();
			parse(key, value);
		}
	}

	private void parse(String key, String value) throws ParseException,
			java.text.ParseException {
		String thisValue = value;
		String[] valids = valid.get(key);
		if (key.equals("help")) {
			printOptions();
			System.exit(0);
		}

		if (valids == null)
			throw new ParseException("Unknown option " + key);

		if (!(key.equals("input.dbpass") || key.equals("input.dbuser")
				|| key.equals("output.dbpass") || key.equals("output.dbuser") || key
				.equals("conf"))) {
			thisValue = thisValue.toLowerCase();
		}

		if (valids[0].equals("list")) {
			for (String s : thisValue.split(",\\s*"))
				if (!Arrays.asList(valids).contains(s))
					throw new ParseException("Invalid option for " + key + ": "
							+ s);
		} else if (Arrays.asList(valids).contains("dir")) {
			if (!thisValue.endsWith("/"))
				thisValue += "/";
			File dir = new File(thisValue);
			if (!dir.exists() || !dir.isDirectory())
				throw new ParseException("Unknown directory " + thisValue);
		} else if (Arrays.asList(valids).contains("nbr")) {
			try {
				Integer.parseInt(thisValue);
			} catch (NumberFormatException e) {
				throw new ParseException("Cannot parse " + thisValue
						+ " as a number.");
			}
		} else if (Arrays.asList(valids).contains("date")) {
			DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
			if (dateend == null) {
				if (thisValue.equals("today")) {
					Calendar cal = Calendar.getInstance();
					dateend = df.format(cal.getTime());
					thisValue = dateend;
				} else {
					dateend = thisValue;
				}
			} else if (datestart == null) {
				datestart = thisValue;
				long tmpDateStart = df.parse(datestart).getTime();
				long tmpDateEnd = df.parse(dateend).getTime();
				if (tmpDateEnd < tmpDateStart) {
					throw new ParseException(
							"Start date is greater than end date");
				}
			}
		} else if (Arrays.asList(valids).contains("file")) {
			File file = new File(thisValue);
			if (!file.exists() || !file.isFile())
				throw new ParseException("Cannot find " + key + " file "
						+ thisValue);
		} else if (!Arrays.asList(valids).contains(thisValue.toLowerCase())
				&& !Arrays.asList(valids).contains("string"))
			throw new ParseException("Invalid option for " + key + ": "
					+ thisValue.toLowerCase());
		put(key, thisValue);
	}

	/**
	 * Parse command-line options.
	 * 
	 * @throws java.text.ParseException
	 */
	public void parse(String[] args) throws ParseException,
			java.text.ParseException {
		for (int i = 0; i < args.length; i++) {
			String key = args[i];
			if (key.startsWith("-"))
				key = key.substring(1);
			else
				throw new ParseException("Option without -: " + key, i);
			if (args.length < i + 1)
				throw new ParseException("Missing value for " + key, i);

			String value = "";
			if (args.length > i + 1)
				value = args[++i];
			parse(key, value);
		}
	}

	public String optionString() {
		String str = "";
		str += "-option [value, DEFAULT] description.\n";
		str += "Values in [] are nominal, values in <>"
				+ " are strings (with default printed).\n\n";
		for (String opt : info.keySet()) {
			String name = opt;
			String def = getProperty(opt);
			String vvec = Arrays.deepToString(valid.get(opt));
			if (!vvec.contains(def))
				vvec = "<" + def + ">";
			else
				vvec = vvec.replaceAll(def, def.toUpperCase());
			String desc = info.get(opt);
			str += "-" + name + " " + vvec + " " + desc + "\n";
		}
		return str;
	}

	public void printOptions() {
		System.out.println(optionString());
	}

	private void addConfItem(String key, String def, String description,
			String[] possible) {
		// Arrays.sort(possible);
		put(key, def);
		info.put(key, description);
		valid.put(key, possible);
	}
}
